/*
 * Paging (virtual memory) support
 * Copyright (c) 2003, Jeffrey K. Hollingsworth <hollings@cs.umd.edu>
 * Copyright (c) 2003,2004 David H. Hovemeyer <daveho@cs.umd.edu>
 * $Revision: 1.55 $
 * 
 * This is free software.  You are permitted to use,
 * redistribute, and modify it as specified in the file "COPYING".
 */

#include <geekos/string.h>
#include <geekos/int.h>
#include <geekos/idt.h>
#include <geekos/kthread.h>
#include <geekos/kassert.h>
#include <geekos/screen.h>
#include <geekos/mem.h>
#include <geekos/malloc.h>
#include <geekos/gdt.h>
#include <geekos/segment.h>
#include <geekos/user.h>
#include <geekos/vfs.h>
#include <geekos/crc32.h>
#include <geekos/paging.h>
#include <math.h>
#include <geekos/bitset.h>
#include <geekos/errno.h>

/* ----------------------------------------------------------------------
 * Public data
 * ---------------------------------------------------------------------- */

struct Paging_Device * g_PagingFile;	/*archivo de paginacion*/
ulong_t * g_PagingVAddr;	/*arreglo de Direcciones virtuales paginadas*/
void * g_BitsetVAddr;	/*bitset que permite administrar rapidamente a g_PagingVAddr*/

/* Page size in KB */ 
#define PAGE_SIZE_KB        (PAGE_SIZE/1024)
/* ----------------------------------------------------------------------
 * Private functions/data
 * ---------------------------------------------------------------------- */

#define SECTORS_PER_PAGE (PAGE_SIZE / SECTOR_SIZE)

/*
 * flag to indicate if debugging paging code
 */
int debugFaults = 0;
#define Debug(args...) if (debugFaults) Print(args)


void checkPaging()
{
  unsigned long reg=0;
  __asm__ __volatile__( "movl %%cr0, %0" : "=a" (reg));
  Print("Paging on ? : %d\n", (reg & (1<<31)) != 0);
}


/*
 * Print diagnostic information for a page fault.
 */
static void Print_Fault_Info(uint_t address, faultcode_t faultCode)
{
    extern uint_t g_freePageCount;

    Print("Pid %d, Page Fault received, at address %x (%d pages free)\n",
        g_currentThread->pid, address, g_freePageCount);
    if (faultCode.protectionViolation)
        Print ("   Protection Violation, ");
    else
        Print ("   Non-present page, ");
    if (faultCode.writeFault)
        Print ("Write Fault, ");
    else
        Print ("Read Fault, ");
    if (faultCode.userModeFault)
        Print ("in User Mode\n");
    else
        Print ("in Supervisor Mode\n");
}

/*
 * Handler for page faults.
 * You should call the Install_Interrupt_Handler() function to
 * register this function as the handler for interrupt 14.
 */
/*static*/ void Page_Fault_Handler(struct Interrupt_State* state)
{
    ulong_t address;
    faultcode_t faultCode;

    KASSERT(!Interrupts_Enabled());

    /* Get the address that caused the page fault */
    address = Get_Page_Fault_Address();
    Debug("Page fault @%lx\n", address);

    /* Get the fault code */
    faultCode = *((faultcode_t *) &(state->errorCode));

    /* rest of your handling code here */
    Print ("Unexpected Page Fault received\n");
    Print_Fault_Info(address, faultCode);
    Dump_Interrupt_State(state);
    /* user faults just kill the process */
    if (!faultCode.userModeFault) KASSERT(0);

    /* For now, just kill the thread/process. */
    Exit(-1);
}

/* ----------------------------------------------------------------------
 * Public functions
 * ---------------------------------------------------------------------- */
 
/**
* Create the directory entry at dirIndex whit the tablePage associated
* Params:
* @pageDir: directory page from user
* @dirIndex: directory index to create the table page
* @tablePage: table entry where allocate the new table page
*
* Returns:
* 0 if successful, or an error code if unsuccessful
*/
int Create_Page_Table( pde_t * pageDir, pte_t ** pageTable, ulong_t vAddr, int flags)
{
	int dirIndex = PAGE_DIRECTORY_INDEX(vAddr);
	if (pageDir[dirIndex].present == 0){    /* la tabla de paginas no esta en memoria*/
		*pageTable = (pte_t *)  Alloc_Page();
		if (*pageTable == 0){
			Print("Error Create_Table_Page_User \n");
			return ENOMEM;
		}
		memset(*pageTable, '\0', PAGE_SIZE);
		pageDir[dirIndex].present = 1;	/*Present: determina si está cargada en un frame en memoria */
		pageDir[dirIndex].flags = flags;
		pageDir[dirIndex].pageTableBaseAddr = (uint_t) PAGE_ALLIGNED_ADDR(*pageTable);
	} else {	/*la tabla de paginas es recuperada de memoria*/
		*pageTable = (pte_t *) ((pageDir[dirIndex].pageTableBaseAddr) << 12);
	}
	return 0;
}

int Create_Page_Table_Kernel( pde_t * pageDir, pte_t ** pageTable, int dirIndex, int flags)
{
	if (pageDir[dirIndex].present == 0){    /* la tabla de paginas no esta en memoria*/
		*pageTable = (pte_t *)  Alloc_Page();
		if (*pageTable == 0){
			Print("Error Create_Table_Page_User \n");
			return ENOMEM;
		}
		memset(*pageTable, '\0', PAGE_SIZE);
		pageDir[dirIndex].present = 1;	/*Present: determina si está cargada en un frame en memoria */
		pageDir[dirIndex].flags = flags;
		pageDir[dirIndex].pageTableBaseAddr = (uint_t) PAGE_ALLIGNED_ADDR(*pageTable);
	} else {	/*la tabla de paginas es recuperada de memoria*/
		*pageTable = (pte_t *) ((pageDir[dirIndex].pageTableBaseAddr) << 12);
	}
	return 0;
}

/**
* Create page for user
* Params:
* @tablePage: user table page
* @tableIndex: table index to create the table page
* @vaddr: virtual address of new page
* @page: page allocated for user process
*
* Returns:
* 0 if successful, or an error code if unsuccessful
*/
int Create_User_Page(pte_t *tablePage, ulong_t vAddr, void **page)
{
	int tableIndex = PAGE_TABLE_INDEX(vAddr);
	if (tablePage[tableIndex].present == 0){    /* la pagina no esta en memoria*/
		*page = Alloc_Pageable_Page(&tablePage[tableIndex], vAddr);
		if (*page == 0){
			Print("Error Create_Page_For_User \n");
			return ENOMEM;
		}
		memset(*page, '\0', PAGE_SIZE);
		tablePage[tableIndex].pageBaseAddr = (uint_t) PAGE_ALLIGNED_ADDR(*page);
		tablePage[tableIndex].present = 1;
		tablePage[tableIndex].flags = VM_USER | VM_READ | VM_WRITE;
	} else {	/*la paginas es recuperada de memoria*/
		*page = (void *) ((tablePage[tableIndex].pageBaseAddr) << 12);
	}
	return 0;
}

/**
 * Initialize virtual memory by building page tables
 * for the kernel and physical memory.
 */
void Init_VM(struct Boot_Info *bootInfo){
    int dirIndex = 0, tableIndex = 0, globalCount = 0, finCiclo = 0;
    pde_t* kernelPageDir = 0; 
    uint_t totalPages = bootInfo->memSizeKB / PAGE_SIZE_KB;	/*calculamos la cantidad de paginas de memoria fisica*/
    kernelPageDir = (pde_t *) Alloc_Page();
    pte_t* kernelPageTable = 0;
	memset((void *)kernelPageDir, '\0',PAGE_SIZE);
	finCiclo = USER_VM_START / PAGE_SIZE;
	Print("finCiclo = %d    totalPages =  %d\n", finCiclo, totalPages);
	
    for (globalCount = 0; globalCount < finCiclo; globalCount++){ //hacerlo hasta los 2 gb y cuando se termina la mem fisica poner los bit de present en 0
		if (Create_Page_Table_Kernel(kernelPageDir, &kernelPageTable, dirIndex, (VM_READ | VM_WRITE)) < 0)
			Exit(ENOMEM);
			
		if(globalCount < totalPages)
			kernelPageTable[tableIndex].present = 1;/*Present: determina si está cargada en un frame en memoria */
		else
			kernelPageTable[tableIndex].present = 0;/*Present:no está cargada en memoria */
		kernelPageTable[tableIndex].flags = (VM_READ | VM_WRITE);
		kernelPageTable[tableIndex].pageBaseAddr = (uint_t) PAGE_ALLIGNED_ADDR(globalCount * PAGE_SIZE);
		
		tableIndex++;
		if(tableIndex == NUM_PAGE_TABLE_ENTRIES){/*si ya cargamos una tabla de paginas completa, debemos pasar a la proxima entrada del directorio*/
		    tableIndex = 0;
		    dirIndex++;
		}
    }
	Enable_Paging(kernelPageDir);
	Install_Interrupt_Handler(14, &Page_Fault_Handler);	
}
/**
 * Initialize paging file data structures.
 * All filesystems should be mounted before this function
 * is called, to ensure that the paging file is available.
 */
void Init_Paging(void)
{
	int numPages = 0;
	g_PagingFile = Get_Paging_Device();
	KASSERT(g_PagingFile != 0);
	numPages = (int)(g_PagingFile->numSectors / SECTORS_PER_PAGE);	/*casteo a entero para redondear*/
	g_PagingVAddr =  Malloc(sizeof(ulong_t)*numPages);
	 memset(g_PagingVAddr, '\0', sizeof(ulong_t)*numPages);
	g_BitsetVAddr = Create_Bit_Set(numPages);
}

/**
 * Find a free bit of disk on the paging file for this page.
 * Interrupts must be disabled.
 * @return index of free page sized chunk of disk space in
 *   the paging file, or -1 if the paging file is full
 */
int Find_Space_On_Paging_File(void)
{
    KASSERT(!Interrupts_Enabled());
    TODO("Find free page in paging file");
}

/**
 * Free a page-sized chunk of disk space in the paging file.
 * Interrupts must be disabled.
 * @param pagefileIndex index of the chunk of disk space
 */
void Free_Space_On_Paging_File(int pagefileIndex)
{
    KASSERT(!Interrupts_Enabled());
    TODO("Free page in paging file");
}

/**
 * Write the contents of given page to the indicated block
 * of space in the paging file.
 * @param paddr a pointer to the physical memory of the page
 * @param vaddr virtual address where page is mapped in user memory
 * @param pagefileIndex the index of the page sized chunk of space
 *   in the paging file
 */
void Write_To_Paging_File(void *paddr, ulong_t vaddr, int pagefileIndex)
{
    struct Page *page = Get_Page((ulong_t) paddr);
    KASSERT(!(page->flags & PAGE_PAGEABLE)); /* Page must be locked! */
    TODO("Write page data to paging file");
}

/**
 * Read the contents of the indicated block
 * of space in the paging file into the given page.
 * @param paddr a pointer to the physical memory of the page
 * @param vaddr virtual address where page will be re-mapped in
 *   user memory
 * @param pagefileIndex the index of the page sized chunk of space
 *   in the paging file
 */
void Read_From_Paging_File(void *paddr, ulong_t vaddr, int pagefileIndex)
{
    struct Page *page = Get_Page((ulong_t) paddr);
    KASSERT(!(page->flags & PAGE_PAGEABLE)); /* Page must be locked! */
    TODO("Read page data from paging file");
}

